<?php

namespace mpend\models;

use Yii;
use yii\db\Exception;
use common\models\app\AppToken;
use common\models\app\AppDevice;
use common\models\app\AppClient;
use common\models\app\AppVersion;
use common\models\user\UserConnect;
use common\helpers\ArrayHelper;
use common\helpers\UuidHelper;

/**
 * 应用访问授权接口
 * 
 * @author emhome <emhome@163.com>
 * @since 1.0
 */
class AuthorizationForm extends \common\base\Model {

    /**
     * @var string 授权应用ID（小程序的APPID）
     */
    public $appid;

    /**
     * @var string 时间戳
     */
    public $timestamp;

    /**
     * @var string 随机字符串
     */
    public $nonce;

    /**
     * @var string 签名字符串
     */
    public $sign;

    /**
     * @var string 客户端平台
     */
    public $platform;

    /**
     * @var string 设备型号
     */
    public $model;

    /**
     * @var string 设备品牌
     */
    public $brand;

    /**
     * @var string 操作系统及版本
     */
    public $system;

    /**
     * @var string 小程序发送的code
     */
    public $build;

    /**
     * @var string 小程序发送的code
     */
    public $code;

    /**
     * @var object 客户端
     */
    private $_client = null;

    /**
     * @var object jscode
     */
    private $_authData = null;

    /**
     * @var object jscode
     */
    private $_version = null;

    /**
     * @inheritdoc
     */
    public function rules() {
        return [
            [['appid', 'build', 'code', 'nonce', 'sign', 'timestamp'], 'required'],
            [['build', 'timestamp'], 'integer'],
            [['model', 'platform', 'system', 'brand'], 'string', 'max' => 64],
            [['nonce'], 'string', 'max' => 32],
            ['appid', 'validateAppId'],
            ['build', 'validateBuild'],
            ['sign', 'validateSignature'],
            ['code', 'validateJscode'],
        ];
    }

    /**
     * 验证APPID是否为授权应用
     * @param string $attribute `appid`
     */
    public function validateAppId($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $client = AppClient::findByAppId($this->$attribute);
        if (!$client) {
            return $this->addError($attribute, '未授权的应用ID');
        }
        $this->_client = $client;
    }

    /**
     * 验证Build版本号是否正确
     * @param string $attribute `build`
     */
    public function validateBuild($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $version = AppVersion::find()->where([
            'build' => $this->$attribute,
            'os' => AppVersion::OS_MP,
        ])->andWhere(['>', 'status', AppVersion::STATUS_DEFAULT])->one();
        if (!$version) {
            $this->addError($attribute, '请求的版本已不提供支持！');
        }
        $this->_version = $version;
    }

    /**
     * 校验请求的签名
     * @param string $attribute `sign`
     */
    public function validateSignature($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $client = $this->getClient();
        $validated = $this->security()->validateSignature($this->attributes, $client->secret);
        if ($validated !== true) {
            $tips = '授权签名校验失败';
            if (YII_DEBUG) {
                $tips .= $validated;
            }
            $this->addError($attribute, $tips);
        }
    }

    /**
     * 校验请求的签名
     * @param string $attribute `sign`
     */
    public function validateJscode($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $authData = $this->getWechatJsCode($this->$attribute);
        if (empty($authData)) {
            return $this->addError($attribute, '获取微信Openid失败');
        }
        $this->_authData = $authData;
    }

    /**
     * 通过code换取微信openid
     * @return \common\models\app\AppClient|null
     */
    protected function getWechatJsCode($code = null) {
        if (!$code) {
            return [];
        }
        if (YII_ENV_DEV) {
            return [
                'openid' => $this->security()->generateUnicode($this->attributes), //	string 	用户唯一标识
                'session_key' => 'YLwaY9z/Wt/1Myd5YrUnsw==', //	string 	会话密钥
            ];
        }
        /* @var $wechat \common\components\wechat\MpWechat */
        $wechat = Yii::$app->wechatMp;
        return $wechat->getJscode2Session($code);
    }

    /**
     * 获取APP授权应用客户端
     * @return AppClient|null
     */
    protected function getClient() {
        return $this->_client;
    }

    /**
     * 获取APP授权应用客户端
     * @return \common\models\app\AppClient|null
     */
    protected function getAuthData() {
        return $this->_authData;
    }

    /**
     * 生成唯一设备号
     * @return \common\models\app\AppClient|null
     */
    protected function generateSN($openid) {
        $string = $this->security()->generateUnicode([
            AppVersion::OS_MP,
            $this->platform,
            $openid,
        ], null);
        return UuidHelper::format(strtoupper($string));
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels() {
        return [
            'appid' => 'AppID',
            'build' => 'Build',
            'nonce' => '随机串',
            'timestamp' => '时间戳',
            'sign' => '签名',
            'code' => '小程序授权CODE',
            'model' => '设备型号',
            'platform' => '客户端平台',
            'brand' => '设备品牌',
            'system' => '操作系统及版本',
        ];
    }

    /**
     * 认证授权操作
     * @inheritdoc
     */
    public function authorize() {
        if (!$this->validate()) {
            return $this->dumpError('授权参数校验失败', null, 401);
        }
        $authData = $this->getAuthData();
        $openid = ArrayHelper::getValue($authData, 'openid');
        $sessionKey = ArrayHelper::getValue($authData, 'session_key');
        if (!$openid) {
            return $this->dumpError('获取微信Openid失败', null, 401);
        }
        //生成设备号
        $sn = $this->generateSN($openid);

        $connect = new WechatConnectForm();
        $connect->client = UserConnect::CLIENT_MP;
        $connect->setAttributes($authData);
        $user = $connect->register();

        //绑定设备并生成访问授权token
        $token = $this->authToken($sn, $user->id);
        return compact('openid', 'sessionKey', 'authData', 'user', 'token');
    }

    /**
     * 数据保存
     * @inheritdoc
     */
    protected function authToken($sn, $userid = 0) {
        $os = AppDevice::OSID_ANDROID;
        $constos = AppDevice::getConstants('osid');
        foreach ($constos as $osname) {
            if (stripos($this->system, $osname) !== false) {
                $os = $osname;
                break;
            }
        }
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $device = AppDevice::findOrNew([
                'sn' => $sn,
                'os_id' => $os,
            ]);
            $device->attributes = [
                'system' => $this->system,
                'brand' => $this->brand,
                'build' => $this->build,
                'model' => $this->model,
                'platform' => $this->platform,
            ];
            if (!$device->save()) {
                throw new Exception('设备注册失败！' . $device->getErrorMsg());
            }
            $token = AppToken::findOrNew([
                'client_id' => $this->client->id,
                'device_id' => $device->id,
            ]);
            $token->user_id = $userid;
            $token->version_id = $this->_version->id;
            $token->generateAccessToken();
            if (!$token->save()) {
                throw new Exception('访问令牌生成失败！' . $token->getErrorMsg());
            }
            $transaction->commit();
        } catch (Exception $e) {
            $transaction->rollBack();
            return $this->dumpError($e->getMessage(), null, 401);
        }
        $data = [
            'access_token' => $token->access_token,
            'expired_at' => $token->expired_at,
        ];
        if (YII_DEBUG) {
            $data['_debug'] = [
                'encrypt_token' => $this->security()->encryptDataByKey($token->access_token)
            ];
        }
        return $data;
    }

}
